{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "369c3444",
   "metadata": {},
   "source": [
    "# ReadtheDocs Retrieval Augmented Generation (RAG) using Zilliz Free Tier"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f6ffd11a",
   "metadata": {},
   "source": [
    "In this notebook, we are going to use Milvus documentation pages to create a chatbot about our product.  The chatbot is going to follow RAG steps to retrieve chunks of data using Semantic Vector Search, then the Question + Context will be fed as a Prompt to a LLM to generate an answer.\n",
    "\n",
    "Many RAG demos use OpenAI for the Embedding Model and ChatGPT for the Generative AI model.  **In this notebook, we will demo a fully open source RAG stack.**\n",
    "\n",
    "Using open-source Q&A with retrieval saves money since we make free calls to our own data almost all the time - retrieval, evaluation, and development iterations.  We only make a paid call to OpenAI once for the final chat generation step. \n",
    "\n",
    "<div>\n",
    "<img src=\"../../images/rag_image.png\" width=\"80%\"/>\n",
    "</div>\n",
    "\n",
    "Let's get started!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "d7570b2e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# For colab install these libraries in this order:\n",
    "# !pip install pymilvus, langchain, torch, transformers, python-dotenv\n",
    "\n",
    "# Import common libraries.\n",
    "import sys, os, time, pprint\n",
    "import numpy as np\n",
    "\n",
    "# Import custom functions for splitting and search.\n",
    "sys.path.append(\"..\")  # Adds higher directory to python modules path.\n",
    "import milvus_utilities as _utils"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fb844837",
   "metadata": {},
   "source": [
    "## Start up a Zilliz free tier cluster.\n",
    "\n",
    "Code in this notebook uses fully-managed Milvus on [Ziliz Cloud free trial](https://cloud.zilliz.com/login).  \n",
    "  1. Choose the default \"Starter\" option when you provision > Create collection > Give it a name > Create cluster and collection.  \n",
    "  2. On the Cluster main page, copy your `API Key` and store it locally in a .env variable.  See note below how to do that.\n",
    "  3. Also on the Cluster main page, copy the `Public Endpoint URI`.\n",
    "\n",
    "💡 Note: To keep your tokens private, best practice is to use an **env variable**.  See [how to save api key in env variable](https://help.openai.com/en/articles/5112595-best-practices-for-api-key-safety). <br>\n",
    "\n",
    "In Jupyter, you also need a .env file (in same dir as notebooks) containing lines like this:\n",
    "- VARIABLE_NAME=value\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "0806d2db",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Type of server: Zilliz Cloud Vector Database(Compatible with Milvus 2.3)\n"
     ]
    }
   ],
   "source": [
    "# STEP 1. CONNECT TO MILVUS\n",
    "\n",
    "# !pip install pymilvus #python sdk for milvus\n",
    "from pymilvus import connections, utility\n",
    "\n",
    "# Jupyter notebooks:\n",
    "# from dotenv import load_dotenv\n",
    "# load_dotenv()\n",
    "# TOKEN = os.getenv(\"ZILLIZ_API_KEY\")\n",
    "\n",
    "# Usual way:\n",
    "from dotenv import load_dotenv, find_dotenv\n",
    "_ = load_dotenv(find_dotenv()) # read local .env file\n",
    "TOKEN = os.environ[\"ZILLIZ_API_KEY\"]\n",
    "\n",
    "# Connect to Zilliz cloud using endpoint URI and API key TOKEN.\n",
    "# TODO change this.\n",
    "CLUSTER_ENDPOINT=\"https://in03-xxxx.api.gcp-us-west1.zillizcloud.com:443\"\n",
    "connections.connect(\n",
    "  alias='default',\n",
    "  #  Public endpoint obtained from Zilliz Cloud\n",
    "  uri=CLUSTER_ENDPOINT,\n",
    "  # API key or a colon-separated cluster username and password\n",
    "  token=TOKEN,\n",
    ")\n",
    "\n",
    "# Check if the server is ready and get colleciton name.\n",
    "print(f\"Type of server: {utility.get_server_version()}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b01d6622",
   "metadata": {},
   "source": [
    "## Load the Embedding Model checkpoint and use it to create vector embeddings\n",
    "**Embedding model:**  We will use the open-source [sentence transformers](https://www.sbert.net/docs/pretrained_models.html) available on HuggingFace to encode the documentation text.  We will download the model from HuggingFace and run it locally. \n",
    "\n",
    "Two model parameters of note below:\n",
    "1. EMBEDDING_DIM refers to the dimensionality or length of the embedding vector. In this case, the embeddings generated for EACH token in the input text will have the SAME length = 1024. This size of embedding is often associated with BERT-based models, where the embeddings are used for downstream tasks such as classification, question answering, or text generation. <br><br>\n",
    "2. MAX_SEQ_LENGTH is the maximum length the encoder model can handle for input sequences. In this case, if sequences longer than 512 tokens are given to the model, everything longer will be (silently!) chopped off.  This is the reason why a chunking strategy is needed to segment input texts into chunks with lengths that will fit in the model's input."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "dd2be7fd",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "device: cpu\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "No sentence-transformers model found with name /Users/christybergman/.cache/torch/sentence_transformers/WhereIsAI_UAE-Large-V1. Creating a new one with MEAN pooling.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'sentence_transformers.SentenceTransformer.SentenceTransformer'>\n",
      "SentenceTransformer(\n",
      "  (0): Transformer({'max_seq_length': 512, 'do_lower_case': False}) with Transformer model: BertModel \n",
      "  (1): Pooling({'word_embedding_dimension': 1024, 'pooling_mode_cls_token': False, 'pooling_mode_mean_tokens': True, 'pooling_mode_max_tokens': False, 'pooling_mode_mean_sqrt_len_tokens': False})\n",
      ")\n",
      "model_name: WhereIsAI/UAE-Large-V1\n",
      "EMBEDDING_DIM: 1024\n",
      "MAX_SEQ_LENGTH: 512\n"
     ]
    }
   ],
   "source": [
    "# STEP 2. DOWNLOAD AN OPEN SOURCE EMBEDDING MODEL.\n",
    "\n",
    "# Import torch.\n",
    "import torch\n",
    "from torch.nn import functional as F\n",
    "from sentence_transformers import SentenceTransformer\n",
    "\n",
    "# Initialize torch settings\n",
    "torch.backends.cudnn.deterministic = True\n",
    "DEVICE = torch.device('cuda:3' if torch.cuda.is_available() else 'cpu')\n",
    "print(f\"device: {DEVICE}\")\n",
    "\n",
    "# Load the model from huggingface model hub.\n",
    "# python -m pip install -U angle-emb\n",
    "model_name = \"WhereIsAI/UAE-Large-V1\"\n",
    "encoder = SentenceTransformer(model_name, device=DEVICE)\n",
    "print(type(encoder))\n",
    "print(encoder)\n",
    "\n",
    "# Get the model parameters and save for later.\n",
    "EMBEDDING_DIM = encoder.get_sentence_embedding_dimension()\n",
    "MAX_SEQ_LENGTH_IN_TOKENS = encoder.get_max_seq_length() \n",
    "# # Assume tokens are 3 characters long.\n",
    "# MAX_SEQ_LENGTH = MAX_SEQ_LENGTH_IN_TOKENS * 3\n",
    "# HF_EOS_TOKEN_LENGTH = 1 * 3\n",
    "# Test with 512 sequence length.\n",
    "MAX_SEQ_LENGTH = MAX_SEQ_LENGTH_IN_TOKENS\n",
    "HF_EOS_TOKEN_LENGTH = 1\n",
    "\n",
    "# Inspect model parameters.\n",
    "print(f\"model_name: {model_name}\")\n",
    "print(f\"EMBEDDING_DIM: {EMBEDDING_DIM}\")\n",
    "print(f\"MAX_SEQ_LENGTH: {MAX_SEQ_LENGTH}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Create a Milvus collection\n",
    "\n",
    "You can think of a collection in Milvus like a \"table\" in SQL databases.  The **collection** will contain the \n",
    "- **Schema** (or [no-schema Milvus client](https://milvus.io/docs/using_milvusclient.md)).  \n",
    "💡 You'll need the vector `EMBEDDING_DIM` parameter from your embedding model.\n",
    "Typical values are:\n",
    "   - 1024 for sbert embedding models\n",
    "   - 1536 for ada-002 OpenAI embedding models\n",
    "- **Vector index** for efficient vector search\n",
    "- **Vector distance metric** for measuring nearest neighbor vectors\n",
    "- **Consistency level**\n",
    "In Milvus, transactional consistency is possible; however, according to the [CAP theorem](https://en.wikipedia.org/wiki/CAP_theorem), some latency must be sacrificed. 💡 Searching movie reviews is not mission-critical, so [`eventually`](https://milvus.io/docs/consistency.md) consistent is fine here.\n",
    "\n",
    "## Add a Vector Index\n",
    "\n",
    "The vector index determines the vector **search algorithm** used to find the closest vectors in your data to the query a user submits.  \n",
    "\n",
    "Most vector indexes use different sets of parameters depending on whether the database is:\n",
    "- **inserting vectors** (creation mode) - vs - \n",
    "- **searching vectors** (search mode) \n",
    "\n",
    "Scroll down the [docs page](https://milvus.io/docs/index.md) to see a table listing different vector indexes available on Milvus.  For example:\n",
    "- FLAT - deterministic exhaustive search\n",
    "- IVF_FLAT or IVF_SQ8 - Hash index (stochastic approximate search)\n",
    "- HNSW - Graph index (stochastic approximate search)\n",
    "- AUTOINDEX - Automatically determined based on OSS vs [Zilliz cloud](https://docs.zilliz.com/docs/autoindex-explained), type of GPU, size of data.\n",
    "\n",
    "Besides a search algorithm, we also need to specify a **distance metric**, that is, a definition of what is considered \"close\" in vector space.  In the cell below, the [`HNSW`](https://github.com/nmslib/hnswlib/blob/master/ALGO_PARAMS.md) search index is chosen.  Its possible distance metrics are one of:\n",
    "- L2 - L2-norm\n",
    "- IP - Dot-product\n",
    "- COSINE - Angular distance\n",
    "\n",
    "💡 Most use cases work better with normalized embeddings, in which case L2 is useless (every vector has length=1) and IP and COSINE are the same.  Only choose L2 if you plan to keep your embeddings unnormalized."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Successfully dropped collection: `wikipedia`\n",
      "Successfully created collection: `wikipedia`\n"
     ]
    }
   ],
   "source": [
    "# STEP 3. CREATE A NO-SCHEMA MILVUS COLLECTION AND DEFINE THE DATABASE INDEX.\n",
    "\n",
    "from pymilvus import MilvusClient\n",
    "\n",
    "# Set the Milvus collection name.\n",
    "COLLECTION_NAME = \"wikipedia\"\n",
    "\n",
    "# Add custom HNSW search index to the collection.\n",
    "# M = max number graph connections per layer. Large M = denser graph.\n",
    "# Choice of M: 4~64, larger M for larger data and larger embedding lengths.\n",
    "M = 16\n",
    "# efConstruction = num_candidate_nearest_neighbors per layer. \n",
    "# Use Rule of thumb: int. 8~512, efConstruction = M * 2.\n",
    "efConstruction = M * 2\n",
    "# Create the search index for local Milvus server.\n",
    "INDEX_PARAMS = dict({\n",
    "    'M': M,               \n",
    "    \"efConstruction\": efConstruction })\n",
    "index_params = {\n",
    "    \"index_type\": \"HNSW\", \n",
    "    \"metric_type\": \"COSINE\", \n",
    "    \"params\": INDEX_PARAMS\n",
    "    }\n",
    "\n",
    "# Use no-schema Milvus client uses flexible json key:value format.\n",
    "# https://milvus.io/docs/using_milvusclient.md\n",
    "mc = MilvusClient(\n",
    "    uri=CLUSTER_ENDPOINT,\n",
    "    # API key or a colon-separated cluster username and password\n",
    "    token=TOKEN)\n",
    "\n",
    "# Check if collection already exists, if so drop it.\n",
    "has = utility.has_collection(COLLECTION_NAME)\n",
    "if has:\n",
    "    drop_result = utility.drop_collection(COLLECTION_NAME)\n",
    "    print(f\"Successfully dropped collection: `{COLLECTION_NAME}`\")\n",
    "\n",
    "# Create the collection.\n",
    "mc.create_collection(COLLECTION_NAME, \n",
    "                     EMBEDDING_DIM,\n",
    "                     consistency_level=\"Eventually\", \n",
    "                     auto_id=True,\n",
    "                     # skip setting params below, if using AUTOINDEX\n",
    "                     params=index_params\n",
    "                    )\n",
    "\n",
    "print(f\"Successfully created collection: `{COLLECTION_NAME}`\")\n",
    "# pprint.pprint(mc.describe_collection(COLLECTION_NAME))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d9bd8153",
   "metadata": {},
   "source": [
    "## Insert data into Milvus\n",
    "\n",
    "For each original text chunk, we'll write the quadruplet (`vector, text, source, h1, h2`) into the database.\n",
    "\n",
    "<div>\n",
    "<img src=\"../../images/db_insert.png\" width=\"80%\"/>\n",
    "</div>\n",
    "\n",
    "**The Milvus Client wrapper can only handle loading data from a list of dictionaries.**\n",
    "\n",
    "Otherwise, in general, Milvus supports loading data from:\n",
    "- pandas dataframes \n",
    "- list of dictionaries\n",
    "\n",
    "Below, we use the embedding model provided by HuggingFace, download its checkpoint, and run it locally as the encoder.  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "454e8348",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Num docs: 1\n",
      "Num chunks: 704\n",
      "Start inserting entities\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 1/1 [00:03<00:00,  3.95s/it]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Milvus Client insert time for 704 vectors: 3.9572505950927734 seconds\n"
     ]
    }
   ],
   "source": [
    "# INSERT WIKIPEDIA CHUNKS INTO A SEPARATE PARTITION.\n",
    "from langchain.document_loaders import WebBaseLoader\n",
    "from langchain.text_splitter import RecursiveCharacterTextSplitter\n",
    "\n",
    "# load the Wikipedia page and create index\n",
    "loader = WebBaseLoader(\"https://en.wikipedia.org/wiki/New_York_City\")\n",
    "docs = loader.load()\n",
    "\n",
    "# Split the documents into smaller chunks\n",
    "text_splitter = RecursiveCharacterTextSplitter(chunk_size=512, chunk_overlap=50)\n",
    "print(f\"Num docs: {len(docs)}\")\n",
    "chunks = text_splitter.split_documents(docs)\n",
    "print(f\"Num chunks: {len(chunks)}\")\n",
    "\n",
    "# Convert chunks to a list of dictionaries.\n",
    "chunk_list = []\n",
    "for chunk in chunks:\n",
    "    # pprint.pprint(chunk)\n",
    "    # Generate embeddings using encoder from HuggingFace.\n",
    "    embeddings = torch.tensor(encoder.encode([chunk.page_content]))\n",
    "    embeddings = np.array(embeddings / np.linalg.norm(embeddings)) #use numpy\n",
    "    converted_values = list(map(np.float32, embeddings))[0]\n",
    "    \n",
    "    # Assemble embedding vector, original text chunk, metadata.\n",
    "    chunk_dict = {\n",
    "        'vector': converted_values,\n",
    "        'chunk': chunk.page_content,\n",
    "        'source': chunk.metadata['source'],\n",
    "        'h1': chunk.metadata['title'][:50],\n",
    "    }\n",
    "    chunk_list.append(chunk_dict)\n",
    "\n",
    "# Insert data into the Milvus collection.\n",
    "print(\"Start inserting entities\")\n",
    "start_time = time.time()\n",
    "insert_result = mc.insert(\n",
    "    COLLECTION_NAME,\n",
    "    data=chunk_list,\n",
    "    append=True,\n",
    "    progress_bar=True)\n",
    "end_time = time.time()\n",
    "print(f\"Milvus Client insert time for {len(chunk_list)} vectors: {end_time - start_time} seconds\")\n",
    "# Milvus Client insert time for 646 vectors: 4.732278823852539 seconds\n",
    "\n",
    "# After final entity is inserted, call flush to stop growing segments left in memory.\n",
    "mc.flush(COLLECTION_NAME)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1746f937",
   "metadata": {},
   "source": [
    "## Define Evaluation Metrics"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "8b3d75c7",
   "metadata": {},
   "outputs": [],
   "source": [
    "import openai, pprint\n",
    "from openai import OpenAI\n",
    "\n",
    "# Define the generation llm model to use.\n",
    "LLM_NAME = \"gpt-3.5-turbo-1106\"\n",
    "TEMPERATURE = 0.1\n",
    "RANDOM_SEED = 415\n",
    "\n",
    "# Reasonable values for the penalty coefficients are around 0.1 to 1 if the aim is to just reduce repition \n",
    "# somewhat. To strongly suppress repetition, set coefficients = 2.\n",
    "FREQUENCY_PENALTY = 2\n",
    "\n",
    "# See how to save api key in env variable.\n",
    "# https://help.openai.com/en/articles/5112595-best-practices-for-api-key-safety\n",
    "openai_client = OpenAI(\n",
    "    # This is the default and can be omitted\n",
    "    api_key=os.environ.get(\"OPENAI_API_KEY\"),\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "b5b6da85",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/Users/christybergman/mambaforge/envs/py311new/lib/python3.11/site-packages/langchain_core/_api/deprecation.py:117: LangChainDeprecationWarning: The class `langchain_community.chat_models.openai.ChatOpenAI` was deprecated in langchain-community 0.0.10 and will be removed in 0.2.0. An updated version of the class exists in the langchain-openai package and should be used instead. To use it run `pip install -U langchain-openai` and import as `from langchain_openai import ChatOpenAI`.\n",
      "  warn_deprecated(\n"
     ]
    }
   ],
   "source": [
    "# Ragas default uses HuggingFace Datasets.\n",
    "# https://docs.ragas.io/en/latest/getstarted/evaluation.html\n",
    "from datasets import Dataset\n",
    "# Ragas default uses OpenAI through LangChain.\n",
    "from langchain.chat_models import ChatOpenAI\n",
    "from ragas.llms import LangchainLLM\n",
    "from ragas import evaluate\n",
    "\n",
    "# Choose the metrics you want to see.\n",
    "from ragas.metrics import (\n",
    "    # Question -> Context metrics\n",
    "    context_recall, \n",
    "    context_precision, \n",
    "    # Context -> Answer metrics\n",
    "    faithfulness, \n",
    "    # Question -> Answer metrics\n",
    "    answer_similarity,\n",
    "    answer_relevancy, \n",
    "    answer_correctness\n",
    "    )\n",
    "metrics = ['context_recall', 'context_precision', 'answer_relevancy', 'faithfulness', 'answer_similarity', 'answer_correctness']\n",
    "\n",
    "# Customize LLM used by Ragas (uses LangChain OpenAI `gpt-3.5-turbo-16k` by default).\n",
    "# Possible to swtich out a HuggingFace open LLM here if you want.\n",
    "# https://docs.ragas.io/en/latest/howtos/customisations/llms.html\n",
    "llm_langchain = ChatOpenAI(model_name=LLM_NAME, temperature=TEMPERATURE)\n",
    "gpt3_wrapper = LangchainLLM(llm=llm_langchain)\n",
    "# Change the default llm for each metric.\n",
    "for metric in metrics:\n",
    "    globals()[metric].llm = gpt3_wrapper"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "5e2db9c0",
   "metadata": {},
   "outputs": [],
   "source": [
    "def assemble_ragas_dataset(input_df, answer_col_name=\"OpenAI_RAG_answer\", context_exists=False, row_number=-9999):\n",
    "    \"\"\"Assemble a RAGAS HuggingFace Dataset from lists of values.\"\"\"\n",
    "\n",
    "    # Subset input_df to the row number.\n",
    "    if row_number >= 0:\n",
    "        subset_df = input_df.iloc[row_number:row_number+1, :]\n",
    "    else:\n",
    "        subset_df = input_df.copy()\n",
    "\n",
    "    question_list = subset_df.Question.to_list()\n",
    "    answer_list = subset_df[answer_col_name].to_list()\n",
    "\n",
    "    # contexts: list[list[str]] - The contexts which were passed into the LLM to answer the question.\n",
    "    if context_exists:\n",
    "        context_list = subset_df.Custom_RAG_context.to_list()\n",
    "        context_list = [[context] for context in context_list]\n",
    "    else:\n",
    "        context_list = [ [\"\"] for _ in question_list]\n",
    "\n",
    "    # ground_truths: list[list[str]] - The ground truth answer to the questions. \n",
    "    truth_list = subset_df.ground_truth_answer.to_list()\n",
    "    truth_list = [[truth] for truth in truth_list]\n",
    "\n",
    "    # Create a HuggingFace Dataset from the ground truth lists.\n",
    "    ragas_ds = Dataset.from_dict({\"question\": question_list,\n",
    "                            \"contexts\": context_list,\n",
    "                            \"answer\": answer_list,\n",
    "                            \"ground_truths\": truth_list})\n",
    "    \n",
    "    return ragas_ds\n",
    "\n",
    "def evaluate_ragas(input_df, answer_col_name=\"OpenAI_RAG_answer\", context_exists=False, row_number=-9999, metrics=\"final_only\"):\n",
    "\n",
    "    # Create a ragas dataset.\n",
    "    ragas_input_ds = assemble_ragas_dataset(input_df, answer_col_name, context_exists, row_number)\n",
    "\n",
    "    # Evaluate the dataset.\n",
    "    if metrics == \"final_only\":\n",
    "        ragas_result = evaluate(\n",
    "            ragas_input_ds,\n",
    "            metrics=[\n",
    "                answer_similarity,\n",
    "                answer_relevancy,\n",
    "                answer_correctness,])\n",
    "    else:\n",
    "        # calculate all metrics\n",
    "        ragas_result = evaluate(\n",
    "            ragas_input_ds,\n",
    "            metrics=[\n",
    "                # Question -> Context metrics\n",
    "                context_recall, \n",
    "                context_precision, \n",
    "                # Context -> Answer metrics\n",
    "                faithfulness, \n",
    "                # Question -> Answer metrics\n",
    "                answer_similarity,\n",
    "                answer_relevancy,\n",
    "                answer_correctness,])\n",
    "        \n",
    "    return ragas_result"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "5d9124c2",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Question</th>\n",
       "      <th>ground_truth_answer</th>\n",
       "      <th>OpenAI_RAG_answer</th>\n",
       "      <th>Custom_RAG_answer</th>\n",
       "      <th>Custom_RAG_context</th>\n",
       "      <th>Uri</th>\n",
       "      <th>H1</th>\n",
       "      <th>H2</th>\n",
       "      <th>Score</th>\n",
       "      <th>Reason</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>What do the parameters for HNSW mean?\\n</td>\n",
       "      <td>- M: maximum degree of nodes in a layer of the...</td>\n",
       "      <td>The HNSW parameters include the “nlist” which ...</td>\n",
       "      <td>The parameters for HNSW have the following mea...</td>\n",
       "      <td>performance, HNSW limits the maximum degree of...</td>\n",
       "      <td>https://pymilvus.readthedocs.io/en/latest/para...</td>\n",
       "      <td>Index</td>\n",
       "      <td>Milvus support to create index to accelerate v...</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>What are HNSW good default parameters when dat...</td>\n",
       "      <td>M=16, efConstruction=32, ef=32</td>\n",
       "      <td>The default HNSW parameters for data size of 2...</td>\n",
       "      <td>For a data size of 25K vectors with a dimensio...</td>\n",
       "      <td>Metrics. Vector Index¶ FLAT IVF_FLAT IVF_SQ8 I...</td>\n",
       "      <td>https://pymilvus.readthedocs.io/en/latest/para...</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>what is the default distance metric used in AU...</td>\n",
       "      <td>Trick answer:  IP inner product, not yet updat...</td>\n",
       "      <td>The default distance metric used in AUTOINDEX ...</td>\n",
       "      <td>The default distance metric used in AUTOINDEX ...</td>\n",
       "      <td>The attributes of collection can be extracted ...</td>\n",
       "      <td>https://pymilvus.readthedocs.io/en/latest/tuto...</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>How did New York City get its name?</td>\n",
       "      <td>In the 1600’s, the Dutch planted a trading pos...</td>\n",
       "      <td>I'm sorry, but I couldn't find any information...</td>\n",
       "      <td>New York City was originally named New Amsterd...</td>\n",
       "      <td>Etymology\\nSee also: Nicknames of New York Cit...</td>\n",
       "      <td>https://en.wikipedia.org/wiki/New_York_City</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                            Question  \\\n",
       "0            What do the parameters for HNSW mean?\\n   \n",
       "1  What are HNSW good default parameters when dat...   \n",
       "2  what is the default distance metric used in AU...   \n",
       "3                How did New York City get its name?   \n",
       "\n",
       "                                 ground_truth_answer  \\\n",
       "0  - M: maximum degree of nodes in a layer of the...   \n",
       "1                     M=16, efConstruction=32, ef=32   \n",
       "2  Trick answer:  IP inner product, not yet updat...   \n",
       "3  In the 1600’s, the Dutch planted a trading pos...   \n",
       "\n",
       "                                   OpenAI_RAG_answer  \\\n",
       "0  The HNSW parameters include the “nlist” which ...   \n",
       "1  The default HNSW parameters for data size of 2...   \n",
       "2  The default distance metric used in AUTOINDEX ...   \n",
       "3  I'm sorry, but I couldn't find any information...   \n",
       "\n",
       "                                   Custom_RAG_answer  \\\n",
       "0  The parameters for HNSW have the following mea...   \n",
       "1  For a data size of 25K vectors with a dimensio...   \n",
       "2  The default distance metric used in AUTOINDEX ...   \n",
       "3  New York City was originally named New Amsterd...   \n",
       "\n",
       "                                  Custom_RAG_context  \\\n",
       "0  performance, HNSW limits the maximum degree of...   \n",
       "1  Metrics. Vector Index¶ FLAT IVF_FLAT IVF_SQ8 I...   \n",
       "2  The attributes of collection can be extracted ...   \n",
       "3  Etymology\\nSee also: Nicknames of New York Cit...   \n",
       "\n",
       "                                                 Uri     H1  \\\n",
       "0  https://pymilvus.readthedocs.io/en/latest/para...  Index   \n",
       "1  https://pymilvus.readthedocs.io/en/latest/para...    NaN   \n",
       "2  https://pymilvus.readthedocs.io/en/latest/tuto...    NaN   \n",
       "3        https://en.wikipedia.org/wiki/New_York_City    NaN   \n",
       "\n",
       "                                                  H2  Score  Reason  \n",
       "0  Milvus support to create index to accelerate v...    NaN     NaN  \n",
       "1                                                NaN    NaN     NaN  \n",
       "2                                                NaN    NaN     NaN  \n",
       "3                                                NaN    NaN     NaN  "
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Read questions and ground truth answers into a pandas dataframe.\n",
    "import pandas as pd\n",
    "\n",
    "# Read ground truth answers from file.\n",
    "eval_df = pd.read_csv(\"../../../christy_coding_scratch/data/milvus_ground_truth.csv\", \n",
    "                      header=0, skip_blank_lines=True)\n",
    "display(eval_df.head())\n",
    "\n",
    "# Get all the questions.\n",
    "question_list = eval_df.Question.to_list()\n",
    "\n",
    "# Get all the ground truth answers.\n",
    "truth_list = eval_df.ground_truth_answer.to_list()\n",
    "\n",
    "# Get all the ground truth sources.\n",
    "uri_list = eval_df.Uri.to_list()\n",
    "\n",
    "# Get all the OpenAI Answers.\n",
    "openai_answer_list = eval_df.OpenAI_RAG_answer.to_list()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bb69c50d",
   "metadata": {},
   "source": [
    "## Define a Custom Execution Loop for RAG."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "9b6aca9b",
   "metadata": {},
   "outputs": [],
   "source": [
    "import requests, json, pprint\n",
    "\n",
    "# Milvus search, define how many retrieval results to return.\n",
    "# Milvus automatically sorts results descending by distance score.\n",
    "TOP_K = 3\n",
    "\n",
    "# Search a collection containing Milvus Documentation.\n",
    "def zilliz_pipeline_collection_search(token, question):\n",
    "    # Define the URL, headers, and data\n",
    "    url = \"https://controller.api.gcp-us-west1.zillizcloud.com/v1/pipelines/pipe-3de3fb4a9bc3c2a64a786b/run\"\n",
    "    headers = {\n",
    "        \"Content-Type\": \"application/json\",\n",
    "        \"Authorization\": f\"Bearer {token}\",\n",
    "    }\n",
    "    data = {\n",
    "        \"data\": {\n",
    "            \"query_text\": question\n",
    "        },\n",
    "        \"params\": {\n",
    "            \"limit\": 3,\n",
    "            \"offset\": 0,\n",
    "            \"outputFields\": [\"chunk_text\", \"chunk_id\", \"doc_name\", \"source\"],\n",
    "            \"filter\": \"chunk_id >= 0 && doc_name == 'param.html'\",\n",
    "        }\n",
    "    }\n",
    "\n",
    "    # Send the POST request\n",
    "    response = requests.post(url, headers=headers, json=data)\n",
    "\n",
    "    # # Print the response\n",
    "    # pprint.pprint(response.json())\n",
    "    return response.json()\n",
    "\n",
    "# Search a collection containing Wikipedia articles about New York City.\n",
    "def wikipedia_search(mc, collection_name, collection_encoder, question, output_fields=None, top_k=3):\n",
    "    # Embed the query\n",
    "    query_embeddings = _utils.embed_query(collection_encoder, [question])\n",
    "\n",
    "    # Define search parameters\n",
    "    INDEX_PARAMS = dict({\n",
    "        'M': M,               \n",
    "        \"efConstruction\": efConstruction })\n",
    "    SEARCH_PARAMS = dict({\n",
    "        \"ef\": INDEX_PARAMS['efConstruction']\n",
    "    })\n",
    "\n",
    "    # Define output fields to return\n",
    "    OUTPUT_FIELDS = [\"h1\", \"source\", \"chunk\"]\n",
    "\n",
    "    # Perform the search\n",
    "    answers = mc.search(\n",
    "        collection_name,\n",
    "        data=query_embeddings, \n",
    "        search_params=SEARCH_PARAMS,\n",
    "        output_fields=output_fields, \n",
    "        filter=\"(source like 'https://en.wikipedia.org%')\",\n",
    "        limit=top_k,\n",
    "        consistency_level=\"Eventually\"\n",
    "    )\n",
    "\n",
    "    return answers"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "cfb1f303",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Function to get OpenAI response and token usage.\n",
    "def get_openai_chat(llm_name, user_prompt, retrieval_context, retrieval_source, message_history,\n",
    "                     temperature=0.0, random_seed=415, frequency_penalty=2):\n",
    "    \"\"\" \n",
    "    Returns 2 pandas dataframes: response, token_use.\n",
    "    \"\"\"\n",
    "    \n",
    "    system_message = f\"\"\"\n",
    "    Use the Context to answer the user's question. Be clear, factual, complete, concise.\n",
    "    If the answer is not in the Context, say \"I don't know\".  Otherwise answer using this format:\n",
    "    Context: {retrieval_context}\n",
    "    Answer: The answer to the question.\n",
    "    Grounding source: {retrieval_source}\n",
    "    \"\"\"\n",
    "    messages = [\n",
    "        {'role': 'system', 'content': system_message},\n",
    "        {'role': 'user', 'content': f\"{user_prompt}\"},\n",
    "        {'role': 'assistant', 'content': f\"Relevant context:\\n{retrieval_context}\"}\n",
    "    ]\n",
    "\n",
    "    # Define the OpenAIEvaluator.\n",
    "    responses = openai_client.chat.completions.create(\n",
    "        response_format={\n",
    "            \"type\": \"json_object\", \n",
    "            # \"schema\": Result.schema_json()\n",
    "        },\n",
    "        messages=message_history + messages,\n",
    "        model=llm_name,\n",
    "        temperature=temperature, # the degree of randomness of the model's output\n",
    "        seed=random_seed,  # for reproducibility\n",
    "        frequency_penalty=frequency_penalty, # allowed amount of repitition in the model's output\n",
    "        # max_tokens=max_tokens # maximum number of tokens the model can output\n",
    "    )\n",
    "    message_history = message_history + messages[1:]\n",
    "\n",
    "    # Make sure total_tokens < 4096.\n",
    "    token_dict = {\n",
    "        'prompt_tokens':responses.usage.prompt_tokens,\n",
    "        'completion_tokens':responses.usage.completion_tokens,\n",
    "        'total_tokens':responses.usage.total_tokens,\n",
    "    }\n",
    "\n",
    "    # Return answer as a JSON object.\n",
    "    openai_response = responses.choices[0].message.content\n",
    "    json_response = json.loads(openai_response)\n",
    "    json_response # single json object with 3 fields\n",
    "\n",
    "    # Create a DataFrame from a list of dictionaries.\n",
    "    response_df = pd.DataFrame([json_response])\n",
    "    token_use_df = pd.DataFrame([token_dict])\n",
    "\n",
    "    return response_df, token_use_df\n",
    "\n",
    "def get_answer_from_openai_chat_response(chat_response):\n",
    "    # Extract the answer from the 0th choice's message content\n",
    "    answer = chat_response.choices[0].message.content\n",
    "    return answer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "d671601b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# STEP1: Moderation check of user question.  If pass, continue.\n",
    "# STEP2: Retrieve closest chunk to question from default collection.\n",
    "#        Check distance score of the retrieved chunk.  \n",
    "#   STEP3:  If score is too low, get the intent from the question.\n",
    "#   STEP4:  Based on question intent, retrieve from a different collection containing that data.\n",
    "# STEP5: Generate answer to the user's question, using context in the ASSISTANT PROMPT.\n",
    "# STEP6: Moderation check of generated answer.  If pass, continue.\n",
    "# STEP7: Return final answer to user.\n",
    "\n",
    "# Define a custom execution loop for RAG.\n",
    "def process_user_message(user_input, question_number, message_history, top_k=3, debug=False):\n",
    "    delimiter = \"```\"\n",
    "    retrieval_done = False\n",
    "    threshold_retrieval_score = 0.6\n",
    "    ragas_metrics= ['answer_relevancy', 'faithfulness']\n",
    "\n",
    "    # # Step 1: Check input to see if it flags the Moderation API or is a prompt injection\n",
    "    # if debug:\n",
    "    #    print()\n",
    "    #    print(\"STEP 1: Check input to see if it flags the Moderation API or is a prompt injection\")\n",
    "    # response = openai_client.moderations.create(input=user_input)\n",
    "    # moderation_output = response.results[0]\n",
    "    # print(moderation_output.flagged) # False\n",
    "\n",
    "    # if moderation_output.flagged:\n",
    "    #     print(\"Step 1: Input flagged by Moderation API.\")\n",
    "    #     return \"Sorry, we cannot process this request.\", message_history\n",
    "\n",
    "    # Step 2: Retrieval from collection #1.\n",
    "    if debug:\n",
    "        print()\n",
    "        print(\"STEP 2: Retrieval from collection #1 MilvusDocs.\")\n",
    "    response = zilliz_pipeline_collection_search(TOKEN, user_input)\n",
    "    distance_score = response['data']['result'][0]['distance']\n",
    "\n",
    "    # Branching logic based on distance score.\n",
    "    if distance_score >= threshold_retrieval_score: \n",
    "        # Extract the retrieval context.\n",
    "        retrieval_context = response['data']['result'][0]['chunk_text']\n",
    "        retrieval_source = response['data']['result'][0]['source']\n",
    "        if debug:\n",
    "            print(f\"DISTANCE SCORE: {distance_score} branch logic.\")\n",
    "            print(f\"chunk_answer: {retrieval_context[:150]}\")\n",
    "        retrieval_done = True\n",
    "\n",
    "    if not retrieval_done and distance_score < threshold_retrieval_score:\n",
    "        # Step 3: If score is too low, get the intent from the prompt.\n",
    "        if debug:\n",
    "            print(f\"DISTANCE SCORE: {distance_score} branching logic...\")\n",
    "            print()\n",
    "            print(\"STEP 3: Score is too low, GET INTENT from the user's question.\")\n",
    "        if \"New York City\" in user_input:\n",
    "            intent = \"new_york\"\n",
    "            print(f\"intent = {intent}\")\n",
    "        # elif could check for other intents here...\n",
    "            \n",
    "        # Step 4: Based on question intent, retrieve from collection containing that data.\n",
    "        if intent == \"new_york\":\n",
    "            if debug:\n",
    "                print()\n",
    "                print(\"STEP 4: Based on question intent, retrieve from collection #2 Wikipedia.\")\n",
    "            OUTPUT_FIELDS = [\"h1\", \"source\", \"chunk\"]\n",
    "            response = wikipedia_search(mc, COLLECTION_NAME, encoder, user_input, OUTPUT_FIELDS, top_k)\n",
    "            # Extract the retrieval score, context, source citation.\n",
    "            distance_score = response[0][0]['distance']\n",
    "            retrieval_context = response[0][0]['entity']['chunk']\n",
    "            retrieval_source = response[0][0]['entity']['source']\n",
    "            if debug:\n",
    "                print(f\"chunk_answer: {retrieval_context[:150]}\")\n",
    "        else:\n",
    "            print(f\"STEP 4: No matching collection for intent {intent}.\")\n",
    "            return \"Sorry, we cannot process this request.\", message_history\n",
    "\n",
    "    # Branching logic based on distance score.\n",
    "    if debug:\n",
    "        print(f\"DISTANCE SCORE: {distance_score} branch logic...\")\n",
    "    if distance_score < threshold_retrieval_score: \n",
    "        print(\"UNABLE TO MATCH INTENT WITH ANY INTERNAL DOC STORE.\")\n",
    "        return \"Sorry, we cannot process this request.\", message_history\n",
    "    else: \n",
    "        print()\n",
    "        print(f\"Score from custom RAG Retrieval is above threshold, proceed to answer generation step.\")\n",
    "        # STEP 5: Generating GPT3.5 answer from the custom execution loop for RAG in the ASSISTANT PROMPT.\n",
    "        if debug:\n",
    "            print()\n",
    "            print(\"STEP 5: Generating GPT3.5 answer from the custom execution loop for RAG in the ASSISTANT PROMPT.\")\n",
    "        system_message = f\"\"\"\n",
    "        Use the Context below to answer the user's question. Be clear, factual, complete, concise.\n",
    "        If the answer is not in the Context, say \"I don't know\".  Otherwise answer using this format:\n",
    "        Context: {retrieval_context}\n",
    "        Answer: The answer to the question.\n",
    "        Grounding source: {retrieval_source}\n",
    "        \"\"\"\n",
    "        messages = [\n",
    "            {'role': 'system', 'content': system_message},\n",
    "            {'role': 'user', 'content': f\"{delimiter}{user_input}{delimiter}\"},\n",
    "            {'role': 'assistant', 'content': f\"Relevant context:\\n{retrieval_context}\"}\n",
    "        ]\n",
    "        final_response = openai_client.chat.completions.create(\n",
    "            messages=message_history + messages,\n",
    "            model=LLM_NAME,\n",
    "            temperature=TEMPERATURE,\n",
    "            seed=RANDOM_SEED,\n",
    "        )\n",
    "        message_history = message_history + messages[1:]\n",
    "        answer = get_answer_from_openai_chat_response(final_response)\n",
    "\n",
    "        # STEP 6: Evaluate whether the chatbot response answers the initial user query well.\n",
    "        if debug:\n",
    "            print()\n",
    "            print(\"STEP 6: Evaluate whether the chatbot response answers the initial user query well.\")\n",
    "            ragas_result = evaluate_ragas(eval_df, \"Custom_RAG_answer\", True, question_number, \"final_only\")\n",
    "            ragas_df = ragas_result.to_pandas()\n",
    "            print(f\"Ragas evaluation: answer similarity: {ragas_df.answer_similarity[0]}, answer relevancy: {np.round(ragas_df.answer_relevancy[0],3)}, answer correctness: {np.round(ragas_df.answer_correctness[0],3)}\")\n",
    "            # could also check for other metrics here...\n",
    "            evaluation_response = \"Y\"\n",
    "\n",
    "        # STEP 7: If LLM answer passed Evaluation, return it to the user.\n",
    "        if evaluation_response == \"Y\":\n",
    "            if debug:\n",
    "                print()\n",
    "                print(\"STEP 7: LLM answer passed Evaluation, return it to the user.\")\n",
    "            return answer, message_history\n",
    "        else:\n",
    "            if debug:\n",
    "                print()\n",
    "                print(f\"STEP 7: The LLM answer does not pass Evaluation.\")\n",
    "            return answer, message_history\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "bb1a52ca",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "question = How did New York City get its name?\n",
      "\n",
      "STEP 2: Retrieval from collection #1 MilvusDocs.\n",
      "DISTANCE SCORE: 0.39108937978744507 branching logic...\n",
      "\n",
      "STEP 3: Score is too low, GET INTENT from the user's question.\n",
      "intent = new_york\n",
      "\n",
      "STEP 4: Based on question intent, retrieve from collection #2 Wikipedia.\n",
      "chunk_answer: New York City traces its origins to Fort Amsterdam and a trading post founded on the southern tip of Manhattan Island by Dutch colonists in approximat\n",
      "DISTANCE SCORE: 0.7961502075195312 branch logic...\n",
      "\n",
      "Score from custom RAG Retrieval is above threshold, proceed to answer generation step.\n",
      "\n",
      "STEP 5: Generating GPT3.5 answer from the custom execution loop for RAG in the ASSISTANT PROMPT.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n",
      "To disable this warning, you can either:\n",
      "\t- Avoid using `tokenizers` before the fork if possible\n",
      "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "STEP 6: Evaluate whether the chatbot response answers the initial user query well.\n",
      "evaluating with [answer_similarity]\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 1/1 [00:00<00:00,  1.49it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "evaluating with [answer_relevancy]\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 1/1 [00:01<00:00,  1.73s/it]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "evaluating with [answer_correctness]\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 1/1 [00:05<00:00,  5.98s/it]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Ragas evaluation: answer similarity: 0.9421961714808575, answer relevancy: 0.894, answer correctness: 0.664\n",
      "\n",
      "STEP 7: LLM answer passed Evaluation, return it to the user.\n",
      "('Answer: New York City was originally named New Amsterdam by Dutch colonists '\n",
      " 'in 1626. However, it was renamed New York in 1664 after King Charles II '\n",
      " 'granted the lands to his brother, the Duke of York, when the city came under '\n",
      " 'British control.')\n"
     ]
    }
   ],
   "source": [
    "# Test the custom RAG execution loop using a question.\n",
    "\n",
    "QUESTION_NUMBER = 3 #2 or 3\n",
    "SAMPLE_QUESTION = question_list[QUESTION_NUMBER]\n",
    "print(f\"question = {SAMPLE_QUESTION}\")\n",
    "\n",
    "truth_answer = truth_list[QUESTION_NUMBER]\n",
    "\n",
    "# Test the OpenAI answer.\n",
    "all_messages = []\n",
    "answer_history = []\n",
    "openai_answer, messages = process_user_message(SAMPLE_QUESTION, QUESTION_NUMBER, all_messages, debug=True)\n",
    "all_messages.append(messages)\n",
    "answer_history.append(openai_answer)\n",
    "pprint.pprint(f\"Answer: {openai_answer}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "67fa1791",
   "metadata": {},
   "source": [
    "## Final Eval Comparisons Custom RAG vs OpenAI RAG"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "aa9a35cd",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "evaluating with [context_recall]\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 1/1 [00:14<00:00, 14.62s/it]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "evaluating with [context_precision]\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 1/1 [00:07<00:00,  7.86s/it]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "evaluating with [faithfulness]\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 1/1 [00:29<00:00, 29.35s/it]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "evaluating with [answer_similarity]\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 1/1 [00:01<00:00,  1.20s/it]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "evaluating with [answer_relevancy]\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 1/1 [00:07<00:00,  7.96s/it]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "evaluating with [answer_correctness]\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 1/1 [00:20<00:00, 20.12s/it]\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>question</th>\n",
       "      <th>ground_truths</th>\n",
       "      <th>contexts_Custom_RAG</th>\n",
       "      <th>answer_Custom_RAG</th>\n",
       "      <th>context_recall</th>\n",
       "      <th>context_precision</th>\n",
       "      <th>faithfulness</th>\n",
       "      <th>answer_similarity_Custom_RAG</th>\n",
       "      <th>answer_relevancy_Custom_RAG</th>\n",
       "      <th>answer_correctness_Custom_RAG</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>What do the parameters for HNSW mean?\\n</td>\n",
       "      <td>[- M: maximum degree of nodes in a layer of th...</td>\n",
       "      <td>[performance, HNSW limits the maximum degree o...</td>\n",
       "      <td>The parameters for HNSW have the following mea...</td>\n",
       "      <td>1.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.8</td>\n",
       "      <td>0.844867</td>\n",
       "      <td>0.979217</td>\n",
       "      <td>0.620304</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>What are HNSW good default parameters when dat...</td>\n",
       "      <td>[M=16, efConstruction=32, ef=32]</td>\n",
       "      <td>[Metrics. Vector Index¶ FLAT IVF_FLAT IVF_SQ8 ...</td>\n",
       "      <td>For a data size of 25K vectors with a dimensio...</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.776006</td>\n",
       "      <td>0.977902</td>\n",
       "      <td>0.622550</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>what is the default distance metric used in AU...</td>\n",
       "      <td>[Trick answer:  IP inner product, not yet upda...</td>\n",
       "      <td>[The attributes of collection can be extracted...</td>\n",
       "      <td>The default distance metric used in AUTOINDEX ...</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.738060</td>\n",
       "      <td>0.990814</td>\n",
       "      <td>0.484557</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>How did New York City get its name?</td>\n",
       "      <td>[In the 1600’s, the Dutch planted a trading po...</td>\n",
       "      <td>[Etymology\\nSee also: Nicknames of New York Ci...</td>\n",
       "      <td>New York City was originally named New Amsterd...</td>\n",
       "      <td>1.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.5</td>\n",
       "      <td>0.942196</td>\n",
       "      <td>0.894259</td>\n",
       "      <td>0.664120</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                            question  \\\n",
       "0            What do the parameters for HNSW mean?\\n   \n",
       "1  What are HNSW good default parameters when dat...   \n",
       "2  what is the default distance metric used in AU...   \n",
       "3                How did New York City get its name?   \n",
       "\n",
       "                                       ground_truths  \\\n",
       "0  [- M: maximum degree of nodes in a layer of th...   \n",
       "1                   [M=16, efConstruction=32, ef=32]   \n",
       "2  [Trick answer:  IP inner product, not yet upda...   \n",
       "3  [In the 1600’s, the Dutch planted a trading po...   \n",
       "\n",
       "                                 contexts_Custom_RAG  \\\n",
       "0  [performance, HNSW limits the maximum degree o...   \n",
       "1  [Metrics. Vector Index¶ FLAT IVF_FLAT IVF_SQ8 ...   \n",
       "2  [The attributes of collection can be extracted...   \n",
       "3  [Etymology\\nSee also: Nicknames of New York Ci...   \n",
       "\n",
       "                                   answer_Custom_RAG  context_recall  \\\n",
       "0  The parameters for HNSW have the following mea...             1.0   \n",
       "1  For a data size of 25K vectors with a dimensio...             0.0   \n",
       "2  The default distance metric used in AUTOINDEX ...             0.0   \n",
       "3  New York City was originally named New Amsterd...             1.0   \n",
       "\n",
       "   context_precision  faithfulness  answer_similarity_Custom_RAG  \\\n",
       "0                1.0           0.8                      0.844867   \n",
       "1                0.0           0.0                      0.776006   \n",
       "2                0.0           0.0                      0.738060   \n",
       "3                1.0           0.5                      0.942196   \n",
       "\n",
       "   answer_relevancy_Custom_RAG  answer_correctness_Custom_RAG  \n",
       "0                     0.979217                       0.620304  \n",
       "1                     0.977902                       0.622550  \n",
       "2                     0.990814                       0.484557  \n",
       "3                     0.894259                       0.664120  "
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Run Ragas Eval for all Questions, all Custom RAG Answers.\n",
    "\n",
    "# def evaluate_ragas(input_df, answer_col_name=\"OpenAI_RAG_answer\", context_exists=False, row_number=-9999, metrics=\"final_only\"):\n",
    "ragas_result = evaluate_ragas(eval_df, \"Custom_RAG_answer\", True, -9999, \"all\")\n",
    "ragas_df_Custom_RAG = ragas_result.to_pandas()\n",
    "\n",
    "# Rename the columns.\n",
    "rename_dict = {\n",
    "    \"contexts\": \"contexts_Custom_RAG\",\n",
    "    \"answer\": \"answer_Custom_RAG\",\n",
    "    \"answer_similarity\": \"answer_similarity_Custom_RAG\",\n",
    "    \"answer_relevancy\": \"answer_relevancy_Custom_RAG\",\n",
    "    \"answer_correctness\": \"answer_correctness_Custom_RAG\"\n",
    "}\n",
    "ragas_df_Custom_RAG.rename(columns=rename_dict, inplace=True)\n",
    "# Reorder the columns.\n",
    "ragas_df_Custom_RAG = ragas_df_Custom_RAG.iloc[:,[0, 3, 1, 2, 4,5,6,7,8,9]]\n",
    "display(ragas_df_Custom_RAG.head())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "1f1b1f4e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "evaluating with [answer_similarity]\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 1/1 [00:00<00:00,  2.01it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "evaluating with [answer_relevancy]\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 1/1 [00:07<00:00,  7.85s/it]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "evaluating with [answer_correctness]\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 1/1 [00:14<00:00, 14.49s/it]\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>question</th>\n",
       "      <th>ground_truths</th>\n",
       "      <th>contexts_OpenAI_RAG</th>\n",
       "      <th>answer_OpenAI_RAG</th>\n",
       "      <th>answer_similarity_OpenAI_RAG</th>\n",
       "      <th>answer_relevancy_OpenAI_RAG</th>\n",
       "      <th>answer_correctness_OpenAI_RAG</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>What do the parameters for HNSW mean?\\n</td>\n",
       "      <td>[- M: maximum degree of nodes in a layer of th...</td>\n",
       "      <td>[]</td>\n",
       "      <td>The HNSW parameters include the “nlist” which ...</td>\n",
       "      <td>0.747939</td>\n",
       "      <td>0.936005</td>\n",
       "      <td>0.186985</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>What are HNSW good default parameters when dat...</td>\n",
       "      <td>[M=16, efConstruction=32, ef=32]</td>\n",
       "      <td>[]</td>\n",
       "      <td>The default HNSW parameters for data size of 2...</td>\n",
       "      <td>0.824929</td>\n",
       "      <td>0.981672</td>\n",
       "      <td>0.206232</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>what is the default distance metric used in AU...</td>\n",
       "      <td>[Trick answer:  IP inner product, not yet upda...</td>\n",
       "      <td>[]</td>\n",
       "      <td>The default distance metric used in AUTOINDEX ...</td>\n",
       "      <td>0.770590</td>\n",
       "      <td>0.990814</td>\n",
       "      <td>0.692648</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>How did New York City get its name?</td>\n",
       "      <td>[In the 1600’s, the Dutch planted a trading po...</td>\n",
       "      <td>[]</td>\n",
       "      <td>I'm sorry, but I couldn't find any information...</td>\n",
       "      <td>0.777967</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.194492</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                            question  \\\n",
       "0            What do the parameters for HNSW mean?\\n   \n",
       "1  What are HNSW good default parameters when dat...   \n",
       "2  what is the default distance metric used in AU...   \n",
       "3                How did New York City get its name?   \n",
       "\n",
       "                                       ground_truths contexts_OpenAI_RAG  \\\n",
       "0  [- M: maximum degree of nodes in a layer of th...                  []   \n",
       "1                   [M=16, efConstruction=32, ef=32]                  []   \n",
       "2  [Trick answer:  IP inner product, not yet upda...                  []   \n",
       "3  [In the 1600’s, the Dutch planted a trading po...                  []   \n",
       "\n",
       "                                   answer_OpenAI_RAG  \\\n",
       "0  The HNSW parameters include the “nlist” which ...   \n",
       "1  The default HNSW parameters for data size of 2...   \n",
       "2  The default distance metric used in AUTOINDEX ...   \n",
       "3  I'm sorry, but I couldn't find any information...   \n",
       "\n",
       "   answer_similarity_OpenAI_RAG  answer_relevancy_OpenAI_RAG  \\\n",
       "0                      0.747939                     0.936005   \n",
       "1                      0.824929                     0.981672   \n",
       "2                      0.770590                     0.990814   \n",
       "3                      0.777967                     0.000000   \n",
       "\n",
       "   answer_correctness_OpenAI_RAG  \n",
       "0                       0.186985  \n",
       "1                       0.206232  \n",
       "2                       0.692648  \n",
       "3                       0.194492  "
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Run Ragas Eval for all Questions, all OpenAI RAG Answers.\n",
    "\n",
    "ragas_result = evaluate_ragas(eval_df, \"OpenAI_RAG_answer\", False, -9999)\n",
    "ragas_df_OpenAI_RAG = ragas_result.to_pandas()\n",
    "\n",
    "# Rename the columns.\n",
    "# Rename the columns.\n",
    "rename_dict = {\n",
    "    \"contexts\": \"contexts_OpenAI_RAG\",\n",
    "    \"answer\": \"answer_OpenAI_RAG\",\n",
    "    \"answer_similarity\": \"answer_similarity_OpenAI_RAG\",\n",
    "    \"answer_relevancy\": \"answer_relevancy_OpenAI_RAG\",\n",
    "    \"answer_correctness\": \"answer_correctness_OpenAI_RAG\"\n",
    "}\n",
    "ragas_df_OpenAI_RAG.rename(columns=rename_dict, inplace=True)\n",
    "# Reorder the columns.\n",
    "ragas_df_OpenAI_RAG = ragas_df_OpenAI_RAG.iloc[:,[0, 3, 1, 2, 4,5,6]]\n",
    "display(ragas_df_OpenAI_RAG)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "c19bc0a5",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>question</th>\n",
       "      <th>ground_truths</th>\n",
       "      <th>contexts_Custom_RAG</th>\n",
       "      <th>answer_Custom_RAG</th>\n",
       "      <th>contexts_OpenAI_RAG</th>\n",
       "      <th>answer_OpenAI_RAG</th>\n",
       "      <th>answer_similarity_Custom_RAG</th>\n",
       "      <th>answer_relevancy_Custom_RAG</th>\n",
       "      <th>answer_correctness_Custom_RAG</th>\n",
       "      <th>answer_similarity_OpenAI_RAG</th>\n",
       "      <th>answer_relevancy_OpenAI_RAG</th>\n",
       "      <th>answer_correctness_OpenAI_RAG</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>What do the parameters for HNSW mean?\\n</td>\n",
       "      <td>[- M: maximum degree of nodes in a layer of th...</td>\n",
       "      <td>[performance, HNSW limits the maximum degree o...</td>\n",
       "      <td>The parameters for HNSW have the following mea...</td>\n",
       "      <td>[]</td>\n",
       "      <td>The HNSW parameters include the “nlist” which ...</td>\n",
       "      <td>0.844867</td>\n",
       "      <td>0.979217</td>\n",
       "      <td>0.620304</td>\n",
       "      <td>0.747939</td>\n",
       "      <td>0.936005</td>\n",
       "      <td>0.186985</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>What are HNSW good default parameters when dat...</td>\n",
       "      <td>[M=16, efConstruction=32, ef=32]</td>\n",
       "      <td>[Metrics. Vector Index¶ FLAT IVF_FLAT IVF_SQ8 ...</td>\n",
       "      <td>For a data size of 25K vectors with a dimensio...</td>\n",
       "      <td>[]</td>\n",
       "      <td>The default HNSW parameters for data size of 2...</td>\n",
       "      <td>0.776006</td>\n",
       "      <td>0.977902</td>\n",
       "      <td>0.622550</td>\n",
       "      <td>0.824929</td>\n",
       "      <td>0.981672</td>\n",
       "      <td>0.206232</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>what is the default distance metric used in AU...</td>\n",
       "      <td>[Trick answer:  IP inner product, not yet upda...</td>\n",
       "      <td>[The attributes of collection can be extracted...</td>\n",
       "      <td>The default distance metric used in AUTOINDEX ...</td>\n",
       "      <td>[]</td>\n",
       "      <td>The default distance metric used in AUTOINDEX ...</td>\n",
       "      <td>0.738060</td>\n",
       "      <td>0.990814</td>\n",
       "      <td>0.484557</td>\n",
       "      <td>0.770590</td>\n",
       "      <td>0.990814</td>\n",
       "      <td>0.692648</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>How did New York City get its name?</td>\n",
       "      <td>[In the 1600’s, the Dutch planted a trading po...</td>\n",
       "      <td>[Etymology\\nSee also: Nicknames of New York Ci...</td>\n",
       "      <td>New York City was originally named New Amsterd...</td>\n",
       "      <td>[]</td>\n",
       "      <td>I'm sorry, but I couldn't find any information...</td>\n",
       "      <td>0.942196</td>\n",
       "      <td>0.894259</td>\n",
       "      <td>0.664120</td>\n",
       "      <td>0.777967</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.194492</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                            question  \\\n",
       "0            What do the parameters for HNSW mean?\\n   \n",
       "1  What are HNSW good default parameters when dat...   \n",
       "2  what is the default distance metric used in AU...   \n",
       "3                How did New York City get its name?   \n",
       "\n",
       "                                       ground_truths  \\\n",
       "0  [- M: maximum degree of nodes in a layer of th...   \n",
       "1                   [M=16, efConstruction=32, ef=32]   \n",
       "2  [Trick answer:  IP inner product, not yet upda...   \n",
       "3  [In the 1600’s, the Dutch planted a trading po...   \n",
       "\n",
       "                                 contexts_Custom_RAG  \\\n",
       "0  [performance, HNSW limits the maximum degree o...   \n",
       "1  [Metrics. Vector Index¶ FLAT IVF_FLAT IVF_SQ8 ...   \n",
       "2  [The attributes of collection can be extracted...   \n",
       "3  [Etymology\\nSee also: Nicknames of New York Ci...   \n",
       "\n",
       "                                   answer_Custom_RAG contexts_OpenAI_RAG  \\\n",
       "0  The parameters for HNSW have the following mea...                  []   \n",
       "1  For a data size of 25K vectors with a dimensio...                  []   \n",
       "2  The default distance metric used in AUTOINDEX ...                  []   \n",
       "3  New York City was originally named New Amsterd...                  []   \n",
       "\n",
       "                                   answer_OpenAI_RAG  \\\n",
       "0  The HNSW parameters include the “nlist” which ...   \n",
       "1  The default HNSW parameters for data size of 2...   \n",
       "2  The default distance metric used in AUTOINDEX ...   \n",
       "3  I'm sorry, but I couldn't find any information...   \n",
       "\n",
       "   answer_similarity_Custom_RAG  answer_relevancy_Custom_RAG  \\\n",
       "0                      0.844867                     0.979217   \n",
       "1                      0.776006                     0.977902   \n",
       "2                      0.738060                     0.990814   \n",
       "3                      0.942196                     0.894259   \n",
       "\n",
       "   answer_correctness_Custom_RAG  answer_similarity_OpenAI_RAG  \\\n",
       "0                       0.620304                      0.747939   \n",
       "1                       0.622550                      0.824929   \n",
       "2                       0.484557                      0.770590   \n",
       "3                       0.664120                      0.777967   \n",
       "\n",
       "   answer_relevancy_OpenAI_RAG  answer_correctness_OpenAI_RAG  \n",
       "0                     0.936005                       0.186985  \n",
       "1                     0.981672                       0.206232  \n",
       "2                     0.990814                       0.692648  \n",
       "3                     0.000000                       0.194492  "
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "####### FINAL SCORES OPENAI RAG vs MILVUS CUSTOM RAG #########\n",
      "LLM as judge model: gpt-3.5-turbo-1106 with temperature: 0.1 scores:\n",
      "# Truth vs RAG answers: 4\n",
      "\n",
      "avg_similarity_Custom_RAG: 0.83\n",
      "avg_similarity_OpenAI_RAG: 0.78\n",
      "\n",
      "answer_relevancy_Custom_RAG: 0.96\n",
      "avg_relevancy_OpenAI_RAG: 0.73\n",
      "\n",
      "avg_correctness_Custom_RAG: 0.6\n",
      "avg_correctness_OpenAI_RAG: 0.32\n"
     ]
    }
   ],
   "source": [
    "# Merge the 2 ragas dfs so they are easier to compare.\n",
    "ragas_merged_df = ragas_df_Custom_RAG.iloc[:,[0,1,2,3,7,8,9]].merge(ragas_df_OpenAI_RAG.iloc[:, 2:], how='inner', left_index=True, right_index=True)\n",
    "# reorder columns\n",
    "ragas_merged_df = ragas_merged_df.iloc[:,[0,1,2,3,7,8,4,5,6,9,10,11]]\n",
    "display(ragas_merged_df.head())\n",
    "\n",
    "print()\n",
    "print(f\"####### FINAL SCORES OPENAI RAG vs MILVUS CUSTOM RAG #########\")\n",
    "print(f\"LLM as judge model: {LLM_NAME} with temperature: {TEMPERATURE} scores:\")\n",
    "print(f\"# Truth vs RAG answers: {len(ragas_merged_df)}\")\n",
    "print()\n",
    "print(f\"avg_similarity_Custom_RAG: {np.round(ragas_merged_df.answer_similarity_Custom_RAG.mean(), 2)}\")\n",
    "print(f\"avg_similarity_OpenAI_RAG: {np.round(ragas_merged_df.answer_similarity_OpenAI_RAG.mean(), 2)}\")\n",
    "print()\n",
    "print(f\"answer_relevancy_Custom_RAG: {np.round(ragas_merged_df.answer_relevancy_Custom_RAG.mean(), 2)}\")\n",
    "print(f\"avg_relevancy_OpenAI_RAG: {np.round(ragas_merged_df.answer_relevancy_OpenAI_RAG.mean(), 2)}\")\n",
    "print()\n",
    "print(f\"avg_correctness_Custom_RAG: {np.round(ragas_merged_df.answer_correctness_Custom_RAG.mean(), 2)}\")\n",
    "print(f\"avg_correctness_OpenAI_RAG: {np.round(ragas_merged_df.answer_correctness_OpenAI_RAG.mean(), 2)}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "d0e81e68",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Drop collection\n",
    "utility.drop_collection(COLLECTION_NAME)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "c777937e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Author: Christy Bergman\n",
      "\n",
      "Python implementation: CPython\n",
      "Python version       : 3.11.6\n",
      "IPython version      : 8.18.1\n",
      "\n",
      "torch                : 2.1.1\n",
      "transformers         : 4.35.2\n",
      "sentence_transformers: 2.2.2\n",
      "pymilvus             : 2.3.4\n",
      "langchain            : 0.1.0\n",
      "openai               : 1.7.2\n",
      "\n",
      "conda environment: py311new\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# Props to Sebastian Raschka for this handy watermark.\n",
    "# !pip install watermark\n",
    "\n",
    "%load_ext watermark\n",
    "%watermark -a 'Christy Bergman' -v -p torch,transformers,sentence_transformers,pymilvus,langchain,openai --conda"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
